package cn.mw.cmdb.util;

import cn.hutool.core.bean.BeanUtil;
import cn.mw.cmdb.config.CustomizeThreadPool;
import cn.mw.cmdb.entity.AssetsInterfaceDTO;
import cn.mw.cmdb.entity.InstanceInfo;
import cn.mw.cmdb.enums.MonitorModeType;
import cn.mw.cmdb.exception.WarnTitleException;
import cn.mw.components.mongodb.tool.CriteriaWrapper;
import cn.mw.microMonitorCommon.api.common.MwEngineManageService;
import cn.mw.microMonitorCommon.api.scan.RuleParam;
import cn.mw.microMonitorCommon.entity.scan.DubboServiceInfo;
import cn.mw.microMonitorCommon.enums.RuleType;
import cn.mw.microMonitorCommon.security.utils.SecurityUtils;
import cn.mw.microMonitorCommon.util.SpringUtils;
import cn.mw.scanengine.entity.ScanEngineInfo;
import cn.mw.scanengine.entity.ScanEngineInfoManage;
import cn.mw.scanengine.service.ScanEngineService;
import cn.mw.scanengine.service.SnmpScanService;
import cn.mw.scanengine.snmp.mib.IfxDTO;
import cn.mw.scanengine.snmp.mib.IpAddrEntry;
import cn.mw.scanengine.snmp.mib.MibIfEntry;
import cn.mw.scanengine.snmp.param.SearchParam;
import cn.mw.scanengine.snmp.scan.model.*;
import cn.mw.scanengine.utils.SNMPUtils;
import cn.mw.scanengine.view.DeviceInfoView;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.mw.cmdb.param.FunctionModuleEnum.enableMonitor;

@Slf4j
public class CMDBScanEngineTool {
    public static List<SearchParam> getSearchParamByInstanceInfo(List<InstanceInfo> instanceInfos) {
        if (null == instanceInfos || instanceInfos.isEmpty()) {
            return null;
        }
        List<SearchParam> searchParams = new ArrayList<>();
        for (InstanceInfo instanceInfo : instanceInfos) {
            SearchParam searchParam = new SearchParam();
            if (!StringUtils.hasText(instanceInfo.getInBandIp()) || null == instanceInfo.getRuleType()) {
                continue;
            }
            if (instanceInfo.getEncrypt()) {
                instanceInfo.decryptInfo();
            }

            searchParam.setIp(instanceInfo.getInBandIp());
            RuleParam ruleParam = new RuleParam();
            ruleParam.setRuleType(instanceInfo.getRuleType());
            ruleParam.setSecurityName(instanceInfo.getSecurityName());
            ruleParam.setSecurityLevel(instanceInfo.getSecurityLevel());
            ruleParam.setPort(instanceInfo.getPort().toString());
            ruleParam.setCommunity(instanceInfo.getCommunity());
            ruleParam.setContextName(instanceInfo.getContextName());
            ruleParam.setAuthProtocol(instanceInfo.getAuthAlgName());
            ruleParam.setAuthToken(instanceInfo.getAuthValue());
            ruleParam.setPrivProtocol(instanceInfo.getPrivAlgName());
            ruleParam.setPrivToken(instanceInfo.getPriValue());
            if (!instanceInfo.getEncrypt()) {
                instanceInfo.encryptInfo();
            }

            searchParam.setRuleParam(ruleParam);

            searchParams.add(searchParam);
        }
        return searchParams;
    }

    public static List<DeviceInfoView> getSnmpDeviceInfoView(ScanEngineService scanEngineService
            , ThreadPoolTaskExecutor executor, List<InstanceInfo> instanceInfos
            , Function<SnmpSearchContext<SnmpScanService>, List<DeviceInfoView>> function, MwEngineManageService mwEngineManageService) throws Exception {

        List<Future<List<DeviceInfoView>>> futureList = getScanData(scanEngineService, executor, instanceInfos, function
                , mwEngineManageService, DeviceInfoView.class, SnmpScanService.class);
        List<DeviceInfoView> deviceInfoViews = null;
        if (null != futureList) {
            for (Future<List<DeviceInfoView>> future : futureList) {
                if (null == deviceInfoViews) {
                    deviceInfoViews = new ArrayList<>();
                }
                List<DeviceInfoView> deviceInfoViewList = future.get();
                if (null != deviceInfoViewList) {
                    deviceInfoViews.addAll(deviceInfoViewList);
                }
            }
        }

        return deviceInfoViews;
    }

    public static <DATA, SERVICE> List<Future<List<DATA>>> getScanData(ScanEngineService scanEngineService
            , ThreadPoolTaskExecutor executor, List<InstanceInfo> instanceInfos, Function<SnmpSearchContext<SERVICE>, List<DATA>> function
            , MwEngineManageService mwEngineManageService, Class<DATA> dataClass, Class<SERVICE> serviceClass) throws Exception {
        //根据是否包含engineId区分实例列表
        List<InstanceInfo> noEngineIdInstanceInfo = new ArrayList<>();
        int noSnmpCount = 0;

        ScanEngineInfoManage scanEngineInfoManage = new ScanEngineInfoManage();
        for (InstanceInfo instanceInfo : instanceInfos) {
            if (!CMDBScanEngineTool.hasSnmpInfo(instanceInfo)) {
                noSnmpCount++;
                continue;
            }
            String engineId = instanceInfo.getPollingEngine();
            if (StringUtils.hasText(engineId)) {
                scanEngineInfoManage.genScanEngineInfos(instanceInfo, mwEngineManageService);
            } else {
                noEngineIdInstanceInfo.add(instanceInfo);
            }
        }

        log.info("instanceInfo size:{} ,noSnmpCount {}", instanceInfos.size(), noSnmpCount);
        log.info("noEngineIdsProcess processing");
        List<Future<List<DATA>>> allEngineIdFutures = noEngineIdsProcess(scanEngineService, executor
                , noEngineIdInstanceInfo, function, dataClass, serviceClass);

        log.info("engineIdsProcess processing");
        List<Future<List<DATA>>> engineIdFutures = engineIdsProcess(scanEngineService, executor, function
                , scanEngineInfoManage, dataClass, serviceClass);

        if(null != engineIdFutures){
            allEngineIdFutures.addAll(engineIdFutures);
        }

        return allEngineIdFutures;
    }

    private static <DATA, SERVICE> List<Future<List<DATA>>> noEngineIdsProcess(ScanEngineService scanEngineService, ThreadPoolTaskExecutor executor
            , List<InstanceInfo> instanceInfos, Function<SnmpSearchContext<SERVICE>, List<DATA>> function
            , Class<DATA> dataClass, Class<SERVICE> serviceClass) throws Exception {

        List<DubboServiceInfo<SERVICE>> snmpScanServices = scanEngineService.getDubboService(serviceClass, null);
        if (null == snmpScanServices) {
            log.info("SnmpScanService is null");
            return null;
        }
        List<SearchParam> searchParams = CMDBScanEngineTool.getSearchParamByInstanceInfo(instanceInfos);
        List<Future<List<DATA>>> futures = null;
        for (DubboServiceInfo<SERVICE> snmpScanService : snmpScanServices) {
            if (null == futures) {
                futures = new ArrayList<>();
            }
            SnmpSearchContext snmpSearchContext = new SnmpSearchContext(snmpScanService.getDubboService(), searchParams ,snmpScanService.getIp());
            Future future = executor.submit(new Callable<List<DATA>>() {
                @Override
                public List<DATA> call() throws Exception {
                    return function.apply(snmpSearchContext);
                }
            });
            futures.add(future);
        }
        return futures;
    }

    private static <DATA, SERVICE> List<Future<List<DATA>>> engineIdsProcess(ScanEngineService scanEngineService, ThreadPoolTaskExecutor executor
            , Function<SnmpSearchContext<SERVICE>, List<DATA>> function, ScanEngineInfoManage<SERVICE> scanEngineInfoManage
            , Class<DATA> dataClass, Class<SERVICE> serviceClass) throws Exception {

        Map<String, List<DubboServiceInfo<SERVICE>>> snmpScanServiceMap = scanEngineService.getDubboServiceMap(serviceClass, scanEngineInfoManage.getEngineIds());
        if (null == snmpScanServiceMap) {
            log.info("snmpScanServiceMap is null");
            return null;
        }

        log.info("snmpScanServiceMap keys:{}" ,snmpScanServiceMap.keySet());
        List<ScanEngineInfo<SERVICE>> scanEngineInfoList = scanEngineInfoManage.getScanEngineInfos();
        for (ScanEngineInfo<SERVICE> scanEngineInfo : scanEngineInfoList) {
            if (scanEngineInfo.isProxyGroup()) {
                for (String engineId : scanEngineInfo.getEngineIds()) {
                    List<DubboServiceInfo<SERVICE>> snmpScanServices = snmpScanServiceMap.get(engineId);
                    if (null != snmpScanServices && !snmpScanServices.isEmpty()) {
                        scanEngineInfo.addSnmpScanServices(engineId, snmpScanServices);
                    }
                }
            } else {
                List<DubboServiceInfo<SERVICE>> snmpScanServices = snmpScanServiceMap.get(scanEngineInfo.getId());
                if (null != snmpScanServices && !snmpScanServices.isEmpty()) {
                    scanEngineInfo.addSnmpScanServices(scanEngineInfo.getId(), snmpScanServices);
                }
            }
            String engineIdInfo = "null";
            if (null != scanEngineInfo) {
                engineIdInfo = scanEngineInfo.toString();
            }
            log.info("scanEngineInfo id:{}, engineIds:{}", scanEngineInfo.getId(), engineIdInfo);
        }
        List<Future<List<DATA>>> futures = null;
        for (ScanEngineInfo<SERVICE> entry : scanEngineInfoList) {
            if (null == futures) {
                futures = new ArrayList<>();
            }
            List<DubboServiceInfo<SERVICE>> snmpScanServices = entry.getSnmpScanServices();
            if (null != snmpScanServices) {
                List<InstanceInfo> instanceInfoList = entry.getScanEngineInfoFetches().stream().map(data -> (InstanceInfo) data).collect(Collectors.toList());
                List<SearchParam> searchParams = getSearchParamByInstanceInfo(instanceInfoList);
                List<String> searchIps = searchParams.stream().map(SearchParam::getIp).collect(Collectors.toList());
                log.info("submit task engineId:{},ips:{}", entry.getId(), searchIps);
                for (DubboServiceInfo<SERVICE> snmpScanService : snmpScanServices) {
                    SnmpSearchContext snmpSearchContext = new SnmpSearchContext(snmpScanService.getDubboService(), searchParams, snmpScanService.getIp());
                    Future future = executor.submit(new Callable<List<DATA>>() {
                        @Override
                        public List<DATA> call() throws Exception {
                            return function.apply(snmpSearchContext);
                        }
                    });
                    futures.add(future);
                }
            }
        }

        return futures;
    }

    public static Map<String, List<AssetsInterfaceDTO>> getIPAssetsInterfacesMap(List<DeviceInfoView> deviceInfoViews
            , VlanType vlanType, boolean debug) {

        Map<String, List<AssetsInterfaceDTO>> map = new HashMap<>();
        if(CollectionUtils.isNotEmpty(deviceInfoViews)){
            for (DeviceInfoView deviceInfo : deviceInfoViews) {
                log.info("接口数据信息::ifxTable:" + deviceInfo.getIfxTable().size()
                        + ";ifxMap:" + deviceInfo.getIfxMap().size()
                        + ";portIfMap:" + deviceInfo.getPortIfMap().size()
                        + ";ifNameMap:" + deviceInfo.getIfNameMap().size());

                List<AssetsInterfaceDTO> retList = new ArrayList<>();
                map.put(deviceInfo.getDeviceIP(), retList);

                Map<Integer, IpAddrEntry> ipAddrEntryMap = new HashMap<>();

                if (null != deviceInfo.getIpAddrTable()) {
                    for (IpAddrEntry ipAddrEntry : deviceInfo.getIpAddrTable()) {
                        ipAddrEntryMap.put(ipAddrEntry.getIpAdEntIfIndex(), ipAddrEntry);
                    }
                }
                if (debug) {
                    log.info("接口描述信息::deviceInfo" + BeanUtil.beanToMap(deviceInfo));
                }
                if (null != deviceInfo.getIfNameMap()) {
                    Long creater = SecurityUtils.getUserId();
                    for (MibIfEntry ifEntry : deviceInfo.getIfNameMap().values()) {
                        if (ifEntry != null) {
                            AssetsInterfaceDTO assetsInterfaceDTO = new AssetsInterfaceDTO();
                            CMDBTool.initCreateInfo(assetsInterfaceDTO ,creater);
                            SNMPUtils.initInterfaceInfoByDeviceInfoView(assetsInterfaceDTO ,ifEntry ,deviceInfo);

                            //设置默认值
                            assetsInterfaceDTO.setPortType(PortType.electrical);

                            IfStatus ifStatus = IfStatus.getIfStatus(ifEntry.getIfOperStatus());
                            assetsInterfaceDTO.setState(ifStatus);
                            IfType ifType = IfType.getIfType(ifEntry.getIfType());
                            assetsInterfaceDTO.setType(ifType);
                            boolean vlanFlag = vlanType.checkVlan(ifEntry.getIfType(), ifEntry.getIfDescr());
                            assetsInterfaceDTO.setVlanFlag(vlanFlag);
                            if (!StringUtils.isEmpty(ifEntry.getIfPhysAddress())) {
                                String mac = SNMPUtils.convertMacFromHexString(ifEntry.getIfPhysAddress());
                                assetsInterfaceDTO.setMac(mac);
                            }
                            IpAddrEntry ipAddrEntry = ipAddrEntryMap.get(ifEntry.getIfIndex());
                            if (null != ipAddrEntry) {
                                assetsInterfaceDTO.setIp(ipAddrEntry.getIpAdEntAddr());
                                assetsInterfaceDTO.setSubnetMask(ipAddrEntry.getIpAdEntNetMask());
                            }

                            IfAddInfo ifAddInfo = deviceInfo.getIfAddInfo(ifEntry.getIfIndex());
                            if (null != ifAddInfo) {
                                assetsInterfaceDTO.setIfMode(ifAddInfo.getIfMode().name());
                                PortType portType = ifAddInfo.getPortType();
                                if (null == portType) {
                                    portType = SNMPUtils.getPortType(ifEntry.getIfDescr());
                                }
                                assetsInterfaceDTO.setPortType(portType);
                            }

                            retList.add(assetsInterfaceDTO);
                        }
                    }
                }
            }
        }
        return map;
    }

    //设置指纹信息并返回唯一查询条件
    public static List<Criteria> setDeviceCode(List<InstanceInfo> instanceInfos, ScanEngineService scanEngineService, MwEngineManageService mwEngineManageService) {
        List<Criteria> uniqueCriterias = new ArrayList<>();
        //按照扫描引擎分组
        Map<String, List<InstanceInfo>> engineIdSnmpMap = new HashMap<>();
        Map<String, InstanceInfo> ipInstanceInfoMap = new HashMap<>();
        List<InstanceInfo> notSnmpInstances = new ArrayList<>();
        for (InstanceInfo instanceInfo : instanceInfos) {

            List<String> functionModule = instanceInfo.getFunctionModule();
            //纳管数据需要判断指纹校验
            if (CollectionUtils.isNotEmpty(functionModule) && functionModule.contains(enableMonitor.getCode())) {
                if (StringUtils.hasText(instanceInfo.getDeviceCode())) {
                    try {
                        Criteria criteria = CriteriaWrapper.where(InstanceInfo::getDeviceCode).is(instanceInfo.getDeviceCode());
                        if (null != criteria) {
                            uniqueCriterias.add(criteria);
                        }
                    } catch (Exception e) {
                        log.error("setDeviceCode {}", e.getMessage());
                    }
                    continue;
                }

                String engineId = (null == instanceInfo.getPollingEngine() ? ScanEngineService.LOCALHOST_NAME : instanceInfo.getPollingEngine());
                List<InstanceInfo> instanceInfoList = engineIdSnmpMap.get(engineId);
                if (null == instanceInfoList) {
                    instanceInfoList = new ArrayList<>();
                    engineIdSnmpMap.put(engineId, instanceInfoList);
                }

                //判断实例是否含有snmp信息
                MonitorModeType ruleType = MonitorModeType.getInfoByName(instanceInfo.getRuleType());
                if (null != ruleType) {
                    switch (ruleType) {
                        case SNMPv1, SNMPv2, SNMPv3:
                            instanceInfoList.add(instanceInfo);
                            ipInstanceInfoMap.put(instanceInfo.getInBandIp(), instanceInfo);
                            break;
                        default:
                            notSnmpInstances.add(instanceInfo);
                    }
                } else {
                    throw new WarnTitleException("ruleType-null-error");
                }
            }
        }

        if (!engineIdSnmpMap.isEmpty()) {
            ThreadPoolTaskExecutor executor = SpringUtils.getBean(CustomizeThreadPool.ScanThreadPool, ThreadPoolTaskExecutor.class);

            //获取snmp指纹信息
            for (Map.Entry<String, List<InstanceInfo>> entry : engineIdSnmpMap.entrySet()) {
                List<InstanceInfo> instanceInfoList = entry.getValue();
                if (!instanceInfoList.isEmpty()) {
                    try {
                        List<DeviceInfoView> deviceInfoViews = getSnmpDeviceInfoView(scanEngineService
                                , executor, instanceInfoList, new Function<SnmpSearchContext<SnmpScanService>, List<DeviceInfoView>>() {
                                    @Override
                                    public List<DeviceInfoView> apply(SnmpSearchContext<SnmpScanService> snmpSearchContext) {
                                        SnmpScanService snmpScanService = snmpSearchContext.getSnmpScanService();
                                        List<SearchParam> searchParams = snmpSearchContext.getSearchParamList();
                                        List<DeviceInfoView> ret = null;
                                        try {
                                            ret = snmpScanService.scanOnlyInterfaceInfo(searchParams);
                                        } catch (Exception e) {
                                            log.error("scanInterfaceInfo", e);
                                        }
                                        return ret;
                                    }
                                }, mwEngineManageService);

                        if (null != deviceInfoViews) {
                            for (DeviceInfoView deviceInfoView : deviceInfoViews) {
                                InstanceInfo instanceInfo = ipInstanceInfoMap.get(deviceInfoView.getDeviceIP());
                                String deviceIDCode = deviceInfoView.getDeviceIDCode();
                                if (null != instanceInfo && StringUtils.hasText(deviceIDCode)) {
                                    instanceInfo.setDeviceCode(deviceIDCode);
                                    Criteria criteria = CriteriaWrapper.where(InstanceInfo::getDeviceCode).is(deviceIDCode);
                                    if (null != criteria) {
                                        uniqueCriterias.add(criteria);
                                    }
                                }
                            }
                        }
                    } catch (Exception e) {
                        log.warn("getSnmpDeviceInfoView ", e);
                    }
                }

            }
        }

        if (notSnmpInstances.isEmpty()) {
            for (InstanceInfo instanceInfo : notSnmpInstances) {
                instanceInfo.setDeviceCode(genDeviceIDCode(instanceInfo));
            }
        }
        return uniqueCriterias;
    }

    public static String genDeviceIDCode(InstanceInfo instanceInfo) {
        StringBuffer sb = new StringBuffer();
        sb.append(instanceInfo.getInBandIp()).append("-")
                .append(instanceInfo.getInstanceName()).append("-")
                .append(instanceInfo.getGroupId()).append("-")
                .append(instanceInfo.getModelId())
        ;
        return sb.toString();
    }

    public static boolean hasSnmpInfo(InstanceInfo instanceInfo) {
        String value = instanceInfo.getRuleType();
        if (!StringUtils.hasText(value)) {
            return false;
        }

        try {
            RuleType ruleType = RuleType.getInfoByName(value);
            boolean flag = false;
            switch (ruleType) {
                case SNMPv1, SNMPv2, SNMPv3:
                    flag = true;
                    break;
            }
            return flag;
        } catch (Exception e) {
            log.warn("instanceInfo id:{},ruleType:{}", instanceInfo.getId(), value);
        }

        return false;
    }
}
